#!/bin/sh

# YAML Master
# 
# 1, Config parametr batch update.
#
# @Author: liaobaikai.com
# @Date:   2024-09-23 ~ 2024-09-26
# @Version: 1.0.1
# ---------------------------------------------
# 
# Config parametr batch update.
# default action is change.
#
# ---------------------------------------------
# https://yaml.org/
# 
UPDATE=2024-09-26
VERSION=1.0.1
# 缩进空格数：2
#INDENT_SPACES=2
SCRIPT_NAME=$0
INPUT_0=.input.yml.tmp
> $INPUT_0

read_from_stdin(){
    # 如果没有指定文件，通过标准输入,CTRL+D结束
    # 如果最后一行没有\n，则无法被读取
    while IFS= read -r line; do
    #echo "STDIN>> $line"
    echo "$line" >> $INPUT_0
    done
    ###
}

Usage(){
    echo "Usage: $SCRIPT_NAME COMMAND OPTIONS

File parsing Master for YAML.

Commands:
  append  Attach to the end of the file
  get     Query the value of the <matcher>
  insert  Insert in front of <matcher> position
  list    List all matchers
          Find the line number to the <matcher> 
  set     Set the value of the <matcher>
  remove  Remove the specified <matcher>

Options:
--yml-file=#         Default YML file   
--input-yml-file=#   Input YML file
--matcher=<matcher>  Matcher
--value=<new_value>  New value
-h, --help           Print help
-v, --version        Print Version

Examples:
# $SCRIPT_NAME append --yml-file=# --input-yml-file=#
  $SCRIPT_NAME append --yml-file=# <stdin::INPUT_YML_FILE> 
  cat <INPUT_YML_FILE> | $SCRIPT_NAME append --yml-file=#

# $SCRIPT_NAME insert --yml-file=# --matcher=<matcher> --input-yml-file=#
  $SCRIPT_NAME insert --yml-file=# --matcher=<matcher> <stdin::INPUT_YML_FILE> 
  cat <INPUT_YML_FILE> | $SCRIPT_NAME insert --yml-file=# --matcher=<matcher>

# $SCRIPT_NAME remove --yml-file=# --matcher=<matcher>

# $SCRIPT_NAME get    --yml-file=# --matcher=<matcher>

# $SCRIPT_NAME set    --yml-file=# --matcher=<matcher> --value=<new_value>
  $SCRIPT_NAME set    --yml-file=# --input-props-file=#

# $SCRIPT_NAME list   --yml-file=#

"
exit 0
}


argument_count=$#
command=$1
shift
for i in $(seq 1 $argument_count); do
    arg="$1"
    shift
    # ...
    val=`echo "$arg" | sed -e 's;^--[^=]*=;;'`
    case "$arg" in
    --matcher=*) MATCHER_0="$val" ;;
    --value=*) VALUE_0="$val" ;;
    --yml-file=*) YML_FILE_0="$val" ;;
    --input-yml-file=*) IN_YML_FILE_0="$val" ;;
    --input-props-file=*) IN_PROPS_FILE_0="$val" ;;
    esac
done


yml_master(){
    awk -v command="$command" -v matcher="$MATCHER_0" -v new_value="$VALUE_0" -v insert_line="$insert_line" -v input_file="$IN_YML_FILE_0" '
# 获取值
function get_cmt_value(s){
    file="YML_VAR.sh" NR
    cmd="sh " file
    rmcmd="rm " file
    printf("YML_VAR=%s\n", s)     > file
    printf("echo \"$YML_VAR\"\n") >> file
    cmd | getline YML_VAR
    system(rmcmd)
    return YML_VAR
}
function get_abs_key(keys, root, sk){
    idx = root - root/2
    if (root == 0) {
        delete keys
    } else {
        len = length(keys)
        for(i=0;i<len;i++){
            if (i > idx) {
                delete keys[idx+1]
            }
        }
    }
    keys[idx] = sk
    len = length(keys)
    p=""
    for(i=0;i<len;i++){
        p = i == 0 ? keys[i] : p "." keys[i]
    }
    return p
}
function ltrim(s){
    sub(/[[:blank:]]*/, "", s)
    return s
}
function is_array_open(s){
    return (s ~ /^[[:blank:]]*-/)
}
function get_key(s){
    # Get key
    idx = index(s, ": ")
    if (idx > 0) {
        sk=substr(s, 0, idx-1)
    } else {
        #   requests:
        if ($0 ~ /:$/) {
            # 当前行无值
            idx = length(s)
            sk=substr(s, 0, idx-1)
        } else {
            # 只有值，比如数组元素
            sk = s
        }
    }
    return ltrim(sk)
}
function get_value(s){
    # 内行存在注释
    return (index(s, "#") > 0 ? get_cmt_value(s) : s)
}
function is_append(command){
    return (command == "append")
}
function is_insert(command){
    return (command == "insert")
}
function is_set(command){
    return (command == "set")
}
function is_get(command){
    return (command == "get")
}
function is_remove(command){
    return (command == "remove")
}
function is_list(command){
    return (command == "list")
}
function is_output(command){
    return (is_append(command) || is_set(command) || is_insert(command) || is_remove(command))
}
function trim_start_key(key, from){
    sub("^[[:blank:]]*"key":?[[:blank:]]*", "", from)
    return from
}
# raw: $0
# sp: string position
# sv: string value
# new_value: string new value
function replace_value(raw, sp, sv, new_value){
    old=substr(raw, 0, sp) "" sv
    new=substr(raw, 0, sp) "" new_value
    # print "old: " old
    # print "new: " new
    old_raw=raw
    sub(old, new, raw)
    if (raw == old_raw) {
        # 没有匹配单引号或双引号
        left = substr(raw, sp + 1, 1)
        right = substr(raw, sp + 1 + length(sv) + 1, 1)
        old=substr(raw, 0, sp) "" left "" sv "" right
        new=substr(raw, 0, sp) "" left "" new_value"" right
        sub(old, new, raw)
    }
    return raw
}

BEGIN{
    is_arr=0
    if (is_set(command)) {
        # set 
        if (input_file != "") {
            # props file
            while((getline var < input_file)==1) {
                idx0 = index(var, "=")
                prop0=substr(var, 0, idx0-1)
                value0=substr(var, idx0+1, length(var))
                setup_props[prop0] = value0 == "" ? " " : value0 
            }
        }
    } else if(is_insert(command)){
        if (input_file != "" && (matcher == "" || insert_line==0)) system(sprintf("cat %s", input_file))
    }
}
$0 ~ /^[[:blank:]]*#/ || NF == 0 {
    if(is_output(command)) print $0
    next
}
{
    if(is_insert(command)){
        if (insert_line == NR) system(sprintf("cat %s", input_file))
        print $0
        next
    }
    raw=$0

    # 数组打开
    if (is_array_open($0)) {
        is_arr = 1
        pos += 1
        space_count=index($0, "- ") + 1
    } else {
        # 数组关闭
        if(index($0, $1) - 1 != space_count){
            is_arr = 0
            pos = -1
        }
    }

    if (is_arr == 1) {
        sub(/[[:blank:]]*- /, "", $0)
        pretty_key = abs_key "[" pos "]"
        if ($1 ~ /:[[:blank:]]*$/) {
            # 对象
            sk = get_key($0)
            sv0 = trim_start_key(sk, $0)
            sp=length(raw)-length(sv0)
            sv = get_value(sv0)
            pretty_key = pretty_key "." sk
        } else {
            # 数组
            sv = get_value($0)
            sk = ""
            sp=0
        }

        if(is_list(command)) {
            if (matcher != "") {
                if (matcher == pretty_key) print NR
            } else {
                print "#" NR "  " pretty_key
            }
            next
        }

        if(is_set(command) && matcher == "") {
            new_value = setup_props[pretty_key]
        }
        
        # print "pretty_key:"pretty_key
        # print "matcher:"matcher
        # 全匹配
        if (pretty_key == matcher || setup_props[pretty_key] != ""){
            if(is_set(command)) {
                if(is_output(command)) print replace_value(raw, sp, sv, new_value)
            } else if(is_get(command)) {
                # 获取数值
                print sv
                next
            } else if(is_list(command)) {
                if (matcher != "") {
                    if (matcher == pretty_key) print NR
                } else {
                    print "#" NR "  " pretty_key
                }
                next
            }
        } else {
            if(is_output(command)) print raw
        }
        # print "#" NR ", abs_key=" abs_key "[" pos "]"", sk=" sk ", value=`"sv"`"
    } else {
        sk = get_key($0)
        # 空格结束索引
        root = index($0, $1) - 1
        abs_key = get_abs_key(keys, root, sk)
        pretty_key=abs_key
        sv0 = trim_start_key(sk, $0)
        sp=length(raw)-length(sv0)
        sv = get_value(sv0)

        if(is_list(command)) {
            # print "pretty_key:"pretty_key
            # print "matcher:"matcher
            if (matcher != "") {
                if (matcher == pretty_key) print NR
            } else {
                print "#" NR "  " pretty_key
            }
            next
        }

        if(is_set(command) && matcher == "") {
            new_value = setup_props[pretty_key]
        }

        # 全匹配
        # print "pretty_key:"pretty_key
        # print "matcher:"matcher
        if (pretty_key == matcher || setup_props[pretty_key] != ""){
            if(is_set(command)) {
                if(is_output(command)) print replace_value(raw, sp, sv, new_value)
            } else if(is_get(command)) {
                # 获取数值
                print sv
                next
            }
        } else {
            if(is_output(command)) print raw
        }

        # print "#" NR ", abs_key=" abs_key ", sk=" sk ", value=`"sv"`"
    }
}
END {
    if(is_append(command)) {
        # print "input_file: " input_file
        if (input_file != "") system(sprintf("cat %s", input_file))
    }
}' $YML_FILE_0


}

case $command in
append) 
    if [ -z "$IN_YML_FILE_0" ]; then
        read_from_stdin
        IN_YML_FILE_0=$INPUT_0
        #echo "[ERROR] Illegal Argument: --input-yml-file is empty." && Usage
    fi
;;
insert) 
    if [ -z "$IN_YML_FILE_0" ]; then
        read_from_stdin
        IN_YML_FILE_0=$INPUT_0
    fi

    [ -z "$MATCHER_0" ] && insert_line=0 || insert_line=$(yml_master)
    
;;
set)
    if [ -z "$IN_PROPS_FILE_0" -a -z "$MATCHER_0" ]; then
        read_from_stdin
        IN_YML_FILE_0=$INPUT_0
    else
        IN_YML_FILE_0=$IN_PROPS_FILE_0
    fi

;;
get | remove | list) ;;
-v|--version) echo "$SCRIPT_NAME Ver $VERSION $UPDATE" && exit 0 ;;
-h|--help|*) Usage ;;
esac

[ -z "$YML_FILE_0" ] && echo "[ERROR] Illegal Argument: --yml-file is empty." && Usage


case $command in
append|insert|set|remove)
    [ -z "$backup_dst" ] && backup_dst="`dirname $YML_FILE_0`"

    file_path=`dirname $YML_FILE_0`
    file_name=`basename $YML_FILE_0`
    swpfile="${file_path}/.${file_name}.swp2"

    yml_master > $swpfile

    # backup file if needed.
    dat=`date "+%Y%m%d%H%M%S"`
    bakfile=${backup_dst}/${file_name%%.*}.copy.${dat}.${YML_FILE_0##*.}

    # echo $bakfile
    mv $YML_FILE_0 $bakfile
    (>&2 echo "Generated backup file $bakfile.")

    # swpfile to normal file.
    mv $swpfile $YML_FILE_0

    exit 0
;;
esac

yml_master
rm $INPUT_0